package com.jiaying.sync;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.ReadPreference;
import com.mongodb.ServerApi;
import com.mongodb.ServerApiVersion;
import com.mongodb.client.*;
import com.mongodb.client.result.InsertOneResult;
import com.mongodb.connection.ClusterDescription;
import com.mongodb.connection.ServerDescription;
import org.bson.Document;

import com.jiaying.sync.config.MongoConfig;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * MongoDB集群测试应用程序
 * 支持主从切换、故障转移和读写分离
 */
public class App {
    private MongoClient mongoClient;
    private MongoConfig config;
    
    public static void main(String[] args) {
        App app = new App();
        try {
            app.initializeConnection();
            // app.testSimpleRoundRobin(); // 测试简单的轮询功能
            app.testFailover();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            app.closeConnection();
        }
    }
    
    /**
     * 初始化MongoDB连接
     * 配置MongoDB客户端设置，包括连接字符串、读偏好和写关注等参数
     */
    public void initializeConnection() {
        try {
            // 初始化配置
            config = new MongoConfig();
            
            // 构建连接字符串
            String connectionString = "mongodb://" + config.getClusterHosts();
            
            // 配置MongoClient设置
            MongoClientSettings settings = MongoClientSettings.builder()
                    .applyConnectionString(new ConnectionString(connectionString))
                    .serverApi(ServerApi.builder().version(ServerApiVersion.V1).build())
                    .applyToClusterSettings(builder -> 
                        builder.serverSelectionTimeout(config.getServerSelectionTimeoutMs(), TimeUnit.MILLISECONDS))
                    .applyToSocketSettings(builder -> 
                        builder.connectTimeout(config.getConnectionTimeoutMs(), TimeUnit.MILLISECONDS)
                               .readTimeout(config.getSocketTimeoutMs(), TimeUnit.MILLISECONDS))
                    .applyToConnectionPoolSettings(builder ->
                        builder.maxSize(config.getMaxConnectionPoolSize())
                               .minSize(config.getMinConnectionPoolSize()))
                    .readPreference(ReadPreference.secondaryPreferred()) // 设置默认读偏好为secondaryPreferred
                    .writeConcern(com.mongodb.WriteConcern.valueOf(config.getWriteConcern())) // 设置写关注
                    .build();
            
            // 创建MongoClient实例
            mongoClient = MongoClients.create(settings);
            
            System.out.println("成功连接到MongoDB集群: " + config.getClusterHosts());
        } catch (Exception e) {
            System.err.println("连接MongoDB集群失败: " + e.getMessage());
            throw e;
        }
    }
    
    /**
     * 测试读写分离功能
     * 演示如何使用secondaryPreferred读偏好从从节点读取数据
     */
    public void testReadWriteSeparation() {
        try {
            MongoDatabase database = mongoClient.getDatabase(config.getDatabaseName());
            MongoCollection<Document> collection = database.getCollection(config.getCollectionName());
            
            // 写操作 - 使用主节点
            System.out.println("执行写操作...");
            Document writeDoc = new Document("name", "testUser")
                    .append("age", 25)
                    .append("timestamp", System.currentTimeMillis());
            
            InsertOneResult result = collection.insertOne(writeDoc);
            System.out.println("写入成功，文档ID: " + result.getInsertedId());
            
            // 读操作 - 使用secondaryPreferred读偏好，支持从多个从节点轮询
            System.out.println("执行读操作（从节点轮询）...");
            MongoCollection<Document> readCollection = collection.withReadPreference(ReadPreference.secondaryPreferred());
            
            // 执行多次读操作来展示轮询效果
            for (int i = 0; i < 5; i++) {
                System.out.println("第" + (i+1) + "次读操作:");
                // 打印readCollection所使用的IP信息
                printReadCollectionServerInfo(readCollection);
                
                Document readDoc = readCollection.find().first();
                if (readDoc != null) {
                    System.out.println("从节点读取数据: " + readDoc.toJson());
                } else {
                    System.out.println("未找到数据");
                }
                System.out.println("---");
                
                // 等待一段时间再进行下一次读操作
                Thread.sleep(1000);
            }
            
            System.out.println("读写分离测试完成");
        } catch (Exception e) {
            System.err.println("读写分离测试失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试从库信息打印功能
     * 展示如何获取和打印MongoDB集群中各节点的信息
     */
    public void testSecondaryServerInfo() {
        try {
            System.out.println("=== 测试从库信息打印功能 ===");
            
            MongoDatabase database = mongoClient.getDatabase(config.getDatabaseName());
            MongoCollection<Document> collection = database.getCollection(config.getCollectionName());
            
            // 使用secondaryPreferred读偏好进行读操作
            MongoCollection<Document> readCollection = collection.withReadPreference(ReadPreference.secondaryPreferred());
            
            // 执行读操作
            Document readDoc = readCollection.find().first();
            if (readDoc != null) {
                // 打印从库信息
                printSecondaryServerInfo();
                System.out.println("从节点读取数据: " + readDoc.toJson());
            } else {
                System.out.println("未找到数据");
            }
            
            System.out.println("从库信息测试完成");
        } catch (Exception e) {
            System.err.println("从库信息测试失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试故障转移功能
     * 模拟在MongoDB集群发生故障转移时应用程序的行为
     */
    public void testFailover() {
        try {
            System.out.println("开始故障转移测试...");
            
            MongoDatabase database = mongoClient.getDatabase(config.getDatabaseName());
            MongoCollection<Document> collection = database.getCollection(config.getCollectionName());
            
            // 进行多次读写操作来测试故障转移
            for (int i = 0; i < 5000; i++) {
                try {
                    // 写操作
                    Document doc = new Document("test", "failover")
                            .append("iteration", i)
                            .append("timestamp", System.currentTimeMillis());
                    
                    InsertOneResult result = insertWithRetry(collection, doc, 3); // 最多重试3次
                    System.out.println("第" + (i+1) + "次写入成功"+result);
                    
                    // 读操作 - 使用secondaryPreferred读偏好，支持从多个从节点轮询
                    MongoCollection<Document> readCollection = collection.withReadPreference(ReadPreference.secondaryPreferred());
                    System.out.println("第" + (i+1) + "次读操作:");
                    // 打印readCollection所使用的IP信息
                    printReadCollectionServerInfo(readCollection);
                    Document readDoc = readCollection.find().sort(new Document("_id", -1)).first();
                    if (readDoc != null) {
                        System.out.println("读取最新数据: " + readDoc.toJson());
                    }
                    
                    System.out.println("---");
                    Thread.sleep(2000); // 等待2秒
                } catch (Exception e) {
                    System.err.println("第" + (i+1) + "次操作失败: " + e.getMessage());
                    // 继续下一次尝试
                }
            }
            
            System.out.println("故障转移测试完成");
        } catch (Exception e) {
            System.err.println("故障转移测试异常: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试简单的从节点轮询功能
     * 使用nearest读偏好来实现从多个从节点之间的负载均衡
     */
    public void testSimpleRoundRobin() {
        try {
            System.out.println("=== 测试简单的从节点轮询功能 ===");
            
            MongoDatabase database = mongoClient.getDatabase(config.getDatabaseName());
            MongoCollection<Document> collection = database.getCollection(config.getCollectionName());
            
            // 使用nearest读偏好进行读操作，这会让MongoDB选择延迟最低的节点
            System.out.println("使用nearest读偏好进行读操作:");
            for (int i = 0; i < 10; i++) {
                MongoCollection<Document> readCollection = collection.withReadPreference(ReadPreference.nearest());
                
                System.out.println("第" + (i+1) + "次读操作:");
                // 打印readCollection所使用的IP信息
                printReadCollectionServerInfo(readCollection);
                
                Document readDoc = readCollection.find().first();
                if (readDoc != null) {
                    System.out.println("从节点读取数据: " + readDoc.toJson());
                } else {
                    System.out.println("未找到数据");
                }
                System.out.println("---");
                
                // 等待一段时间再进行下一次读操作
                Thread.sleep(1000);
            }
            
            System.out.println("简单的从节点轮询测试完成");
        } catch (Exception e) {
            System.err.println("简单的从节点轮询测试失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试从节点轮询功能
     * 使用nearest读偏好来实现从多个从节点之间的负载均衡
     */
    public void testSecondaryRoundRobin() {
        try {
            System.out.println("=== 测试从节点轮询功能 ===");
            
            MongoDatabase database = mongoClient.getDatabase(config.getDatabaseName());
            MongoCollection<Document> collection = database.getCollection(config.getCollectionName());
            
            // 使用nearest读偏好进行读操作，这会让MongoDB选择延迟最低的节点
            MongoCollection<Document> readCollection = collection.withReadPreference(ReadPreference.nearest());
            
            System.out.println("执行多次读操作来展示轮询效果:");
            // 执行多次读操作来展示轮询效果
            for (int i = 0; i < 10; i++) {
                System.out.println("第" + (i+1) + "次读操作:");
                // 打印readCollection所使用的IP信息
                printReadCollectionServerInfo(readCollection);
                
                Document readDoc = readCollection.find().first();
                if (readDoc != null) {
                    System.out.println("从节点读取数据: " + readDoc.toJson());
                } else {
                    System.out.println("未找到数据");
                }
                System.out.println("---");
                
                // 等待一段时间再进行下一次读操作
                Thread.sleep(1000);
            }
            
            System.out.println("从节点轮询测试完成");
        } catch (Exception e) {
            System.err.println("从节点轮询测试失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 测试真正的从节点轮询功能（通过创建多个连接）
     * 通过创建多个MongoClient实例来实现更真实的轮询效果
     */
    public void testTrueSecondaryRoundRobin() {
        MongoClient[] clients = new MongoClient[3];
        try {
            System.out.println("=== 测试真正的从节点轮询功能 ===");
            
            // 创建多个MongoClient实例
            for (int i = 0; i < 3; i++) {
                String connectionString = "mongodb://" + config.getClusterHosts();
                clients[i] = MongoClients.create(connectionString);
            }
            
            MongoDatabase database = clients[0].getDatabase(config.getDatabaseName());
            MongoCollection<Document> collection = database.getCollection(config.getCollectionName());
            
            System.out.println("执行多次读操作来展示真正的轮询效果:");
            // 执行多次读操作来展示轮询效果
            for (int i = 0; i < 10; i++) {
                // 轮询使用不同的客户端连接
                MongoClient currentClient = clients[i % 3];
                MongoDatabase currentDatabase = currentClient.getDatabase(config.getDatabaseName());
                MongoCollection<Document> currentCollection = currentDatabase.getCollection(config.getCollectionName());
                
                // 使用secondaryPreferred读偏好
                MongoCollection<Document> readCollection = currentCollection.withReadPreference(ReadPreference.secondaryPreferred());
                
                System.out.println("第" + (i+1) + "次读操作 (使用客户端 " + (i % 3 + 1) + "):");
                // 打印readCollection所使用的IP信息
                printReadCollectionServerInfo(readCollection);
                
                Document readDoc = readCollection.find().first();
                if (readDoc != null) {
                    System.out.println("从节点读取数据: " + readDoc.toJson());
                } else {
                    System.out.println("未找到数据");
                }
                System.out.println("---");
                
                // 等待一段时间再进行下一次读操作
                Thread.sleep(1000);
            }
            
            System.out.println("真正的从节点轮询测试完成");
        } catch (Exception e) {
            System.err.println("真正的从节点轮询测试失败: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // 关闭所有客户端连接
            for (int i = 0; i < clients.length; i++) {
                if (clients[i] != null) {
                    clients[i].close();
                }
            }
        }
    }
    
    /**
     * 打印从库服务器信息
     * 获取并打印MongoDB集群中所有节点的信息
     */
    private void printSecondaryServerInfo() {
        try {
            ClusterDescription clusterDescription = mongoClient.getClusterDescription();
            List<ServerDescription> serverDescriptions = clusterDescription.getServerDescriptions();
            
            System.out.println("当前集群中的服务器:");
            for (ServerDescription serverDesc : serverDescriptions) {
                System.out.println("  服务器地址: " + serverDesc.getAddress() + 
                                  ", 类型: " + serverDesc.getType() + 
                                  ", 状态: " + serverDesc.getState() +
                                  (serverDesc.isPrimary() ? " (主节点)" : " (从节点)"));
            }
            
            // 查找从节点
            ServerDescription secondaryServer = null;
            for (ServerDescription serverDesc : serverDescriptions) {
                if (serverDesc.getType().toString().contains("SECONDARY") && 
                    serverDesc.getState().toString().equals("CONNECTED")) {
                    secondaryServer = serverDesc;
                    break;
                }
            }
            
            if (secondaryServer != null) {
                System.out.println("当前读操作使用的从库: " + secondaryServer.getAddress());
            } else {
                System.out.println("当前未找到可用的从库");
            }
        } catch (Exception e) {
            System.err.println("获取服务器信息失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 打印readCollection所使用的IP信息
     * 获取并打印当前读操作所使用的MongoDB节点信息
     * @param readCollection 当前的MongoCollection实例
     */
    private void printReadCollectionServerInfo(MongoCollection<Document> readCollection) {
        try {
            // 获取集群描述信息
            ClusterDescription clusterDescription = mongoClient.getClusterDescription();
            List<ServerDescription> serverDescriptions = clusterDescription.getServerDescriptions();
            
            // 根据读偏好设置确定使用的服务器
            String readPreference = readCollection.getReadPreference().getName();
            // System.out.println("当前读偏好设置: " + readPreference);
            
            // 查找实际用于读操作的服务器
            ServerDescription readServer = null;
            if ("secondaryPreferred".equals(readPreference)) {
                // 查找从节点
                for (ServerDescription serverDesc : serverDescriptions) {
                    if (serverDesc.getType().toString().contains("SECONDARY") && 
                        serverDesc.getState().toString().equals("CONNECTED")) {
                        readServer = serverDesc;
                        break;
                    }
                }
                // 如果没有从节点，则使用主节点
                if (readServer == null) {
                    for (ServerDescription serverDesc : serverDescriptions) {
                        if (serverDesc.isPrimary() && 
                            serverDesc.getState().toString().equals("CONNECTED")) {
                            readServer = serverDesc;
                            break;
                        }
                    }
                }
            } else if ("primary".equals(readPreference)) {
                // 查找主节点
                for (ServerDescription serverDesc : serverDescriptions) {
                    if (serverDesc.isPrimary() && 
                        serverDesc.getState().toString().equals("CONNECTED")) {
                        readServer = serverDesc;
                        break;
                    }
                }
            } else if ("nearest".equals(readPreference)) {
                // 查找延迟最低的节点
                long minRoundTripTime = Long.MAX_VALUE;
                for (ServerDescription serverDesc : serverDescriptions) {
                    if (serverDesc.getState().toString().equals("CONNECTED")) {
                        if (readServer == null || serverDesc.getRoundTripTimeNanos() < minRoundTripTime) {
                            readServer = serverDesc;
                            minRoundTripTime = serverDesc.getRoundTripTimeNanos();
                        }
                    }
                }
            }
            
            if (readServer != null) {
                System.out.println("readCollection实际使用的服务器IP: " + readServer.getAddress());
            } else {
                System.out.println("未找到可用的服务器用于读操作");
            }
        } catch (Exception e) {
            System.err.println("获取readCollection服务器信息失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * 带重试机制的插入操作
     * 在写操作失败时自动重试，提高系统的容错能力
     * @param collection MongoDB集合
     * @param document 要插入的文档
     * @param maxRetries 最大重试次数
     * @return 插入结果
     */
    private InsertOneResult insertWithRetry(MongoCollection<Document> collection, Document document, int maxRetries) {
        int attempts = 0;
        while (attempts < maxRetries) {
            try {
                return collection.insertOne(document);
            } catch (Exception e) {
                attempts++;
                System.err.println("写操作失败，尝试次数: " + attempts + ", 错误: " + e.getMessage());
                if (attempts >= maxRetries) {
                    throw e; // 达到最大重试次数，抛出异常
                }
                try {
                    // 等待一段时间再重试
                    Thread.sleep(2000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("重试过程中被中断", ie);
                }
            }
        }
        throw new RuntimeException("写操作重试失败");
    }
    
    /**
     * 关闭连接
     * 释放MongoDB客户端连接资源
     */
    public void closeConnection() {
        if (mongoClient != null) {
            mongoClient.close();
            System.out.println("MongoDB连接已关闭");
        }
    }
}